from neo4j import GraphDatabase
from neo4j.exceptions import Neo4jError
from typing import List, Any, Union, Dict
from core.testAgent.Node import Clazz, Method, TestClazz, _convert_node
import threading
from core.testAgent.LoggerManager import LoggerManager

# danger dependency
from core.testAgent.Config import *


class CKGRetriever:
    """单例模式的 CKGRetriever，管理 Neo4j 连接"""
    _instance = None
    _lock = threading.Lock()  # 线程锁，确保线程安全

    def __new__(cls, uri, user, password):
        with cls._lock:
            if cls._instance is None:
                cls._instance = super(CKGRetriever, cls).__new__(cls)
                cls._instance._init_driver(uri, user, password)
        return cls._instance

    def _init_driver(self, uri, user, password):
        """初始化 Neo4j 驱动"""
        self.driver = GraphDatabase.driver(uri, auth=(user, password))
        self.focal_method_id = None  # 存储 element_id (字符串) 或 None

    def close(self):
        """关闭 Neo4j 连接"""
        if self.driver:
            self.driver.close()

    def change_focal_method_id(self, focal_method_id):
        """
        更换当前的 focal_method_id。
        """
        self.focal_method_id = focal_method_id

    # def run_query(self, query: str, parameters: dict) -> List[Any]:
    #     """
    #     通用查询方法，运行 Cypher 查询并返回结果。
    #
    #     :param query: Cypher 查询字符串
    #     :param parameters: 查询参数字典
    #     :return: 查询结果的记录列表
    #     """
    #     try:
    #         with self.driver.session() as session:
    #             return [record for record in session.run(query, parameters)]
    #     except Neo4jError as e:
    #         print(f"Neo4j query failed: {e}")
    #         return []

    def lazy_load_classes(self, batch_size=10):
        """
        Generator to lazily load clazz nodes from the Neo4j database.

        :param batch_size: Number of nodes to fetch in each batch.
        :return: Yields clazz nodes one by one.
        """
        with self.driver.session() as session:
            offset = 0
            while True:
                # Run a Cypher query with LIMIT and SKIP for pagination
                result = session.run(
                    """
                    MATCH (c:Clazz) 
                    RETURN elementId(c) AS node_id, c
                    ORDER BY c.name ASC
                    SKIP $offset LIMIT $batch_size
                    """,
                    offset=offset, batch_size=batch_size
                )

                # Convert results to a list
                result = list(result)

                # If no more results, stop iteration
                if not result:
                    break

                # Yield each clazz one by one
                for record in result:
                    yield _convert_node(record["c"])

                # Move to the next batch
                offset += batch_size

    def load_methods(self, fake_number=10, focal_method=True):
        """
        Generator to lazily load method nodes from the Neo4j database.

        :param focal_method: return only focal method if True which have public access modifier and not abstract
        :param batch_size: Number of nodes to fetch in each batch.
        :return: Yields method nodes one by one.
        """
        with self.driver.session() as session:
            # Run a Cypher query with LIMIT and SKIP for pagination
            #     WHERE m.modifiers CONTAINS "public" AND NOT m.modifiers CONTAINS "abstract" AND m.type = "METHOD"
            if focal_method:
                result = session.run(
                    """
                    MATCH (m:Method) 
                    WHERE m.modifiers CONTAINS "public" AND NOT m.modifiers CONTAINS "abstract" AND m.type = "METHOD"
                    RETURN elementId(m) AS node_id, m
                    ORDER BY m.name ASC
                    """
                )
                result = list(result)
            else:
                result = session.run(
                    """
                    MATCH (m:Method) 
                    RETURN elementId(m) AS node_id, m
                    ORDER BY m.name ASC
                    """
                )
                result = list(result)
            return [_convert_node(record["m"]) for record in result]

    def load_filtered_methods(self):
        """
        Load 100 pseudo-randomly selected method nodes from the Neo4j database,
        filtered by modifiers and sorted deterministically by a hash of the node ID.

        :return: A list of converted method nodes.
        """
        with self.driver.session() as session:
            result = session.run(
                """
                MATCH (m:Method)
                WHERE m.modifiers CONTAINS "public" 
                  AND NOT m.modifiers CONTAINS "abstract" 
                  AND m.type = "METHOD"
                RETURN elementId(m) AS node_id, m
                ORDER BY elementId(m)  // deterministic sort
                LIMIT 200
                """
            )
            result = list(result)
            return [_convert_node(record["m"]) for record in result]

    def lazy_load_variables(self, batch_size=10):
        """
        Generator to lazily load variable nodes from the Neo4j database.

        :param batch_size: Number of nodes to fetch in each batch.
        :return: Yields variable nodes one by one.
        """
        with self.driver.session() as session:
            offset = 0
            while True:
                # Run a Cypher query with LIMIT and SKIP for pagination
                result = session.run(
                    """
                    MATCH (v:Variable) 
                    RETURN elementId(v) AS node_id, v
                    ORDER BY v.name ASC
                    SKIP $offset LIMIT $batch_size
                    """,
                    offset=offset, batch_size=batch_size
                )
                result = list(result)

                # Convert results to a list
                variables = list(result)

                # If no more results, stop iteration
                if not variables:
                    break

                # Yield each variable one by one
                for record in variables:
                    yield _convert_node(record["v"])

                # Move to the next batch
                offset += batch_size

    def lazy_load_test_classes(self, batch_size=10):
        """
        Generator to lazily load test class nodes from the Neo4j database.

        :param batch_size: Number of nodes to fetch in each batch.
        :return: Yields test class nodes one by one.
        """
        with self.driver.session() as session:
            offset = 0
            while True:
                # Run a Cypher query with LIMIT and SKIP for pagination
                result = session.run(
                    """
                    MATCH (t:TestClazz) 
                    RETURN elementId(t) AS node_id, t
                    ORDER BY t.name ASC
                    SKIP $offset LIMIT $batch_size
                    """,
                    offset=offset, batch_size=batch_size
                )
                result = list(result)

                # Convert results to a list
                test_classes = list(result)

                # If no more results, stop iteration
                if not test_classes:
                    break

                # Yield each test class one by one
                for record in test_classes:
                    yield _convert_node(record["t"])

                # Move to the next batch
                offset += batch_size

    def update_class(self, node_id, field_value):
        """
        更新指定的 Clazz 节点的 summarization 字段。
        :param node_id:
        :param field_value:
        :return:
        """
        with self.driver.session() as session:
            session.run(
                """
                MATCH (c:Clazz)
                WHERE elementId(c) = $node_id
                SET c.summarization = $field_value
                """,
                node_id=node_id,
                field_value=field_value
            )
        return

    def update_method(self, node_id, field_value):
        """
        更新指定的 Method 节点的 summarization 字段。
        :param node_id:
        :param field_value:
        :return:
        """
        with self.driver.session() as session:
            session.run(
                """
                MATCH (m:Method)
                WHERE elementId(m) = $node_id
                SET m.summarization = $field_value
                """,
                node_id=node_id,
                field_value=field_value
            )
        return

    def update_variable(self, node_id, field_value):
        """
        更新指定的 Variable 节点的 summarization 字段。
        :param node_id:
        :param field_value:
        :return:
        """
        with self.driver.session() as session:
            session.run(
                """
                MATCH (v:Variable)
                WHERE elementId(v) = $node_id
                SET v.summarization = $field_value
                """,
                node_id=node_id,
                field_value=field_value
            )
        return

    def update_test_class(self, name, tested_clazz_fqname, tested_method_fqname, method_signature, test_report,
                          coverage_rate, coverage_lines, mutation_score, mutants, find_bug, bug_report, requirement):
        """
        更新指定的 TestClazz 节点，包括 tested_clazz_FQname, test_report, coverage_rate,
        coverage_lines, mutation_score, mutants 字段。
        """
        with self.driver.session() as session:
            session.run(
                """
                MATCH (t:TestClazz)
                WHERE t.name = $name
                SET t.tested_clazz_FQname = $tested_clazz_fqname,
                    t.tested_method_FQname = $tested_method_fqname,
                    t.method_signature = $method_signature,
                    t.test_report = $test_report,
                    t.coverage_rate = $coverage_rate,
                    t.coverage_lines = $coverage_lines,
                    t.mutation_score = $mutation_score,
                    t.mutants = $mutants,
                    t.find_bug = $find_bug,
                    t.bug_report = $bug_report,
                    t.requirement = $requirement
                """,
                name=name,
                tested_clazz_fqname=tested_clazz_fqname,
                tested_method_fqname=tested_method_fqname,
                method_signature=method_signature,
                test_report=test_report,
                coverage_rate=coverage_rate,
                coverage_lines=coverage_lines,
                mutation_score=mutation_score,
                mutants=mutants,
                find_bug=find_bug,
                bug_report=bug_report,
                requirement=requirement
            )
        return

    def load_current_method(self):
        """
        加载当前 focal_method_id 对应的 Method 节点。
        :return:
        """
        if not self.focal_method_id:
            return None
            
        with self.driver.session() as session:
            result = session.run(
                """
                MATCH (m:Method)
                WHERE elementId(m) = $focal_method_id
                RETURN m
                """,
                focal_method_id=self.focal_method_id
            )
            result = list(result)
            return _convert_node(result[0]["m"]) if result else None

    def search_similarity_test_class(self, test_class_name: str) -> List[tuple]:
        """
        基于Jaccard相似度搜索相似的测试类。
        """

        def jaccard_similarity(set1, set2):
            intersection = len(set1.intersection(set2))
            union = len(set1.union(set2))
            return intersection / union if union != 0 else 0

        with self.driver.session() as session:
            # 获取指定测试类的相关节点集合
            result = session.run(
                """
                MATCH (t:TestClazz {name: $test_class_name}) 
                RETURN t.related_nodes AS related_nodes
                """,
                test_class_name=test_class_name
            )
            record = result.single()
            if not record:
                return None

            target_related_nodes = set(record["related_nodes"])

            # 获取所有其他测试类的相关节点集合
            result = session.run(
                """
                MATCH (t:TestClazz)
                RETURN t AS test_class, t.related_nodes AS related_nodes
                """
            )

            similarities = []
            for record in result:
                test_node = _convert_node(record["test_class"])
                if test_node.name == test_class_name:
                    continue
                assert isinstance(test_node, TestClazz)
                related_nodes = set(test_node.related_nodes)
                similarity = jaccard_similarity(target_related_nodes, related_nodes)
                if similarity < 0.1:
                    continue
                similarities.append((test_node, similarity))

            # 按相似度排序
            similarities.sort(key=lambda x: x[1], reverse=True)
            return similarities

    def search_constructor_in_clazz(self, clazz_name: str) -> List[Any]:
        """
        查询指定类中的构造方法。
        :param clazz_name:
        :return:
        """
        with self.driver.session() as session:
            result = session.run(
                """
                MATCH (n:Method {type:"CONSTRUCTOR"})<-[:CONTAIN]-(c:Clazz {name: $clazz_name})
                RETURN n
                """,
                clazz_name=clazz_name
            )
            result = list(result)
            return [_convert_node(record["n"]) for record in result]

    def method_calls_query(self, method_name: str, full_qualified_name: str = None, method_params: List[str] = None) -> \
            Union[str, List[Any]]:
        """
        查询指定的 Method 节点调用的 Method 节点。

        :param method_name: Method 节点的 name 属性
        :param full_qualified_name: Method 节点的 full_qualified_name 属性
        :param method_params: Method 节点的 params 属性（列表）
        :return: 1. "NO MATCH FOR SOURCE METHOD" : 没有找到源 Method 节点
                2. "MULTIPLE MATCHES FOR SOURCE METHOD" : 找到多个源 Method 节点
                3. "NO MATCH FOR TARGET METHOD" : 没有找到目标 Method 节点
                4. List : 调用的 Method 节点的列表
        """
        # Step 1: 查询 Method 节点
        method_nodes = self.search_method_query(method_name, full_qualified_name, method_params)

        # 验证是否找到 Method 节点
        if not method_nodes:
            LoggerManager().logger.error(f"No Method node found for name='{method_name}'.")
            # print(
            #     f"[ERROR] No Method node found for name='{method_name}'.")
            return "NO MATCH FOR SOURCE METHOD"

        # 验证找到的 Method 节点是否唯一
        if len(method_nodes) > 1:
            LoggerManager().logger.error(f"Multiple Method nodes found for name='{method_name}', full_qualified_name='{full_qualified_name}'.")
            # print(
            #     f"[ERROR] Multiple Method nodes found for name='{method_name}', full_qualified_name='{full_qualified_name}'.")
            return "MULTIPLE MATCHES FOR SOURCE METHOD"

        # 获取唯一 Method 节点
        method_node = method_nodes[0]

        # Step 2: 使用唯一 Method 节点 ID 查询 INVOKE 出边
        with self.driver.session() as session:
            result = session.run(
                """
                MATCH (m:Method)-[:INVOKE]->(target)
                WHERE elementId(m) = $method_id
                RETURN target
                """,
                method_id=method_node.id
            )
            result = list(result)

        tuples = [_convert_node(record["target"]) for record in result]

        # 验证是否找到相关的目标 Method 节点
        if not tuples:
            LoggerManager().logger.warning(f"No valid InvokeLine and Method pairs found for Method ID: {method_node.id}.")
            # print(
            #     f"[INFO] Calls query: No valid InvokeLine and Method pairs found for Method ID: {method_node.id}.")
            return "NO MATCH FOR TARGET METHOD"
        return tuples

    def method_usages_query(self, method_name: str, full_qualified_name: str, method_params: List[str]) -> Union[str, List[Any]]:
        """
        查询指定方法节点的所有被调节点，并返回相关的列表。

        :param method_name: 方法名
        :param full_qualified_name: 方法的全限定名
        :param method_params: 方法参数列表
        :return: 1. "NO MATCH FOR SOURCE METHOD" : 没有找到源 Method 节点
                2. "MULTIPLE MATCHES FOR SOURCE METHOD" : 找到多个源 Method 节点
                3. "NO MATCH FOR TARGET METHOD" : 没有找到目标 Method 节点
                4. List : 调用的节点的列表，包含 Method, Variable 节点
        """
        # Step 1: 唯一确定 Method 节点并验证
        method_nodes = self.search_method_query(method_name, full_qualified_name, method_params)

        # 验证是否找到 Method 节点
        if not method_nodes:
            LoggerManager().logger.error(f"No Method node found for name='{method_name}'.")
            # print(
            #     f"[ERROR] No Method node found for name='{method_name}'.")
            return "NO MATCH FOR SOURCE METHOD"

        # 验证找到的 Method 节点是否唯一
        if len(method_nodes) > 1:
            LoggerManager().logger.error(f"Multiple Method nodes found for name='{method_name}', full_qualified_name='{full_qualified_name}'.")
            # print(
            #     f"[ERROR] Multiple Method nodes found for name='{method_name}', full_qualified_name='{full_qualified_name}'.")
            return "MULTIPLE MATCHES FOR SOURCE METHOD"

        # 获取唯一 Method 节点
        method_node = method_nodes[0]

        # Step 2: 查询所有通过 INVOKE 指向该方法的节点
        with self.driver.session() as session:
            result = session.run(
                """
                MATCH (target)-[:INVOKE]->(m:Method)
                WHERE elementId(m) = $method_id
                RETURN target
                """,
                method_id=method_node.id
            )
            result = list(result)
        # 过滤掉 type 值为 "TEST_METHOD" 的 Method 节点
        filtered_tuples = []
        for record in result:
            node = _convert_node(record["target"])
            if Limit_Retrieve_Test_Case and isinstance(node, Method) and getattr(node, "type", None) == "TEST_METHOD":
                continue
            filtered_tuples.append(node)

        tuples = filtered_tuples

        # 验证是否找到相关的目标 Method 节点
        if not tuples:
            LoggerManager().logger.warning(f"No valid InvokeLine and Method pairs found for Method ID: {method_node.id}.")
            # print(
            #     f"[INFO] Usage query: No valid InvokeLine and Method pairs found for Method ID: {method_node.id}.")
            return "NO MATCH FOR TARGET METHOD"
        return tuples

    def search_clazz_query(self, clazz_name: str, full_qualified_name: str = None) -> Union[str, Clazz]:
        """
        查询指定的 Clazz 节点。

        :param full_qualified_name: Clazz 节点的 full_qualified_name 属性
        :param clazz_name: Clazz 节点的 name 属性
        :return: 1. "NO MATCH FOR SOURCE CLAZZ" : 没有找到源 Clazz 节点
                2. "MULTIPLE MATCHES FOR SOURCE CLAZZ" : 找到多个源 Clazz 节点
                3. Clazz : 查询到的 Clazz 节点
        """

        if not full_qualified_name:
            query = f"""
            MATCH (node:Clazz)
            WHERE node.name = $clazz_name
            RETURN node
            """
        else:
            query = f"""
            MATCH (node:Clazz)
            WHERE node.name = $clazz_name
            AND node.full_qualified_name contains $full_qualified_name
            RETURN node
            """
        if '.' in clazz_name:
            clazz_name = clazz_name.split('.')[-1]
        with self.driver.session() as session:
            result = session.run(query, {"clazz_name": clazz_name, "full_qualified_name": full_qualified_name})
            result = list(result)

        if not result:
            LoggerManager().logger.error(f"No nodes found for the given clazz name: {clazz_name}.")
            # print(f"No nodes found for the given clazz name: {clazz_name}.")
            return "NO MATCH FOR SOURCE CLAZZ"

        if len(result) > 1:
            LoggerManager().logger.error(f"Multiple nodes found for the given clazz name: {clazz_name}.")
            # print(f"Multiple nodes found for the given clazz name: {clazz_name}.")
            return "MULTIPLE MATCHES FOR SOURCE CLAZZ"

        return _convert_node(result[0]["node"])

    def search_parent_clazz_query(self, clazz_name: str) -> Union[str, List[Clazz]]:
        """
        查询指定 Clazz 节点的父类 Clazz 节点。(包括EXTEND与IMPLEMENT关系)
        :param clazz_name:
        :return: 1. "NO MATCH FOR PARENT CLAZZ" : 没有找到父类 Clazz 节点
                2. List : 父类 Clazz 节点列表
        """
        with self.driver.session() as session:
            result = session.run(
                """
                MATCH (n:Clazz {name: $name})-[:EXTEND|IMPLEMENT]->(m:Clazz)
                RETURN m
                """,
                name=clazz_name
            )
            result = list(result)
        if not result:
            LoggerManager().logger.error(f"No parent nodes found for the given clazz name: {clazz_name}.")
            # print(f"No parent nodes found for the given clazz name: {clazz_name}.")
            return "NO MATCH FOR PARENT CLAZZ"

        return [_convert_node(record["m"]) for record in result]

    def search_sub_clazz_query(self, clazz_name: str) -> Union[str, List[Clazz]]:
        """
        查询指定 Clazz 节点的子类 Clazz 节点。(包括EXTEND与IMPLEMENT关系)
        :param clazz_name:
        :return: 1. "NO MATCH FOR SUB CLAZZ" : 没有找到子类 Clazz 节点
                2. List : 子类 Clazz 节点列表
        """
        with self.driver.session() as session:
            result = session.run(
                """
                MATCH (n:Clazz {name: $name})<-[:EXTEND|IMPLEMENT]-(m:Clazz)
                RETURN m
                """,
                name=clazz_name
            )
            result = list(result)

        if not result:
            LoggerManager().logger.error(f"No sub nodes found for the given clazz name: {clazz_name}.")
            # print(f"No sub nodes found for the given clazz name: {clazz_name}.")
            return "NO MATCH FOR SUB CLAZZ"

        return [_convert_node(record["m"]) for record in result]

    def search_method_by_id(self, method_id: int) -> Union[str, Method]:
        """
        查询指定的 Method 节点（通过内部整数 ID）。
        :param method_id: Neo4j 内部整数 ID
        :return: 1. "NO MATCH FOR SOURCE METHOD" : 没有找到源 Method 节点
                2. Method : 查询到的 Method 节点
        """
        with self.driver.session() as session:
            result = session.run(
                """
                MATCH (m:Method)
                WHERE id(m) = $method_id
                RETURN m
                """,
                method_id=method_id
            )
            result = list(result)
        if not result:
            LoggerManager().logger.error(f"No nodes found for the given method ID: {method_id}.")
            # print(f"No nodes found for the given method ID: {method_id}.")
            return "NO MATCH FOR SOURCE METHOD"

        return _convert_node(result[0]["m"])

    def search_method_by_signature(self, method_signature: str) -> Union[str, Method]:
        """
        查询指定的 Method 节点。
        :param method_signature:
        :return: 1. "NO MATCH FOR SOURCE METHOD" : 没有找到源 Method 节点
                2. Method : 查询到的 Method 节点
        """
        with self.driver.session() as session:
            result = session.run(
                """
                MATCH (m:Method)
                WHERE m.signature = $signature
                RETURN m
                """,
                signature=method_signature
            )
            result = list(result)
        if not result:
            LoggerManager().logger.error(f"No nodes found for the given method signature: {method_signature}.")
            # print(f"No nodes found for the given method signature: {method_signature}.")
            return "NO MATCH FOR SOURCE METHOD"

        return _convert_node(result[0]["m"])

    def search_test_class_by_signature(self, method_signature: str, fqname: str) -> Union[str, List[TestClazz]]:
        """
        根据方法签名查询测试该方法的测试类节点。
        :param method_signature:
        :return: 1. "NO MATCH FOR SOURCE TEST CLASS" : 没有找到源 TestClazz 节点
                2. TestClazz : 查询到的 TestClazz 节点
        """
        with self.driver.session() as session:
            result = session.run(
                """
                MATCH (t:TestClazz)
                WHERE t.method_signature = $signature AND t.tested_method_FQname = $fqname
                RETURN t
                """,
                signature=method_signature,
                fqname=fqname
            )
            result = list(result)
        if not result:
            LoggerManager().logger.error(f"No Test Clazz nodes found for testing the given method signature: {method_signature}.")
            # print(f"No nodes found for the given method signature: {method_signature}.")
            return "NO MATCH FOR SOURCE TEST CLASS"

        return [_convert_node(record["t"]) for record in result]

    def delete_test_class_by_signature(self, method_signature: str, fqname: str) -> dict:
        """
        根据方法签名删除测试该方法的测试类节点，并返回详细删除信息。

        :param method_signature: 被测试方法的签名
        :return: 包含操作是否成功、删除的测试类数量、删除的关系数量等信息的字典
        """
        with self.driver.session() as session:
            result = session.run(
                """
                MATCH (t:TestClazz)
                WHERE t.method_signature = $signature AND t.tested_method_FQname = $fqname
                OPTIONAL MATCH (t)-[r]-()
                DELETE r, t
                RETURN count(t) AS deleted_nodes, count(r) AS deleted_rels
                """,
                signature=method_signature, fqname=fqname
            )
            record = result.single()
            deleted_nodes = record["deleted_nodes"]
            deleted_rels = record["deleted_rels"]

            return {
                "found": deleted_nodes > 0,
                "deleted_nodes": deleted_nodes,
                "deleted_relationships": deleted_rels,
                "success": deleted_nodes > 0
            }

    def search_method_query(self, method_name: str, full_qualified_name: str = None, method_params: List[str] = None) \
            -> Union[str, List[Method]]:
        """
        查询指定的 Method 节点。
        :param method_name:
        :param full_qualified_name:
        :param method_params:
        :return: 1. "NO MATCH FOR SOURCE METHOD" : 没有找到源 Method 节点
                2. List : 查询到的 Method 节点列表
        """

        # flag 为 0 表示第一次查询中仅使用了 method_name 参数
        # 为 1 表示使用了 method_name 和 full_qualified_name 参数
        # 为 2 表示使用了 method_name 和 method_params 参数
        # 为 3 表示使用了 method_name, full_qualified_name 和 method_params 参数
        flag = 0
        # 第一次查询：检查是否可以直接找到节点
        with self.driver.session() as session:
            # 基础查询部分
            query = "MATCH (m:Method)"
            # 动态添加条件
            conditions = []
            if '.' not in method_name:
                conditions.append(f"m.name = '{method_name}'")
            else:
                conditions.append(f"m.full_qualified_name CONTAINS '{method_name}'")
            if full_qualified_name:
                conditions.append(f"m.full_qualified_name CONTAINS '{full_qualified_name}'")
                flag += 1
            if method_params:
                conditions.append(f"m.params = {method_params}")
                flag += 2
            # 将条件拼接到查询语句
            if conditions:
                query += " WHERE " + " AND ".join(conditions)
            # 返回最终查询
            query += " RETURN m"

            result = session.run(query)
            result = list(result)
        if len(result) == 1:
            return [_convert_node(record["m"]) for record in result]
        if len(result) > 1:
            node_ids = [record["m"].element_id for record in result]
            if self.focal_method_id in node_ids:
                node_ids.remove(self.focal_method_id)
            with self.driver.session() as session:
                sub_result = session.run(
                    """
                    UNWIND $node_ids AS node_id
                    MATCH (base:Method), (m)
                    WHERE elementId(m) = node_id AND elementId(base) = $base_method_id
                    MATCH path = shortestPath((base)-[*..8]-(m))
                    RETURN m, length(path) AS distance
                    ORDER BY distance
                    """,
                    node_ids=node_ids,
                    base_method_id=self.focal_method_id
                )
                sub_result = list(sub_result)
            return [_convert_node(record["m"]) for record in sub_result]

        if len(result) == 0 and flag == 0:
            LoggerManager().logger.error(f"No nodes found for the given method name: {method_name}.")
            # print(f"No nodes found for the given method name: {method_name}.")
            return "NO MATCH FOR SOURCE METHOD"

        if len(result) == 0 and flag > 0:
            with self.driver.session() as session:
                result = session.run(
                    """
                    MATCH (m:Method)
                    WHERE m.name = $name
                    RETURN m
                    """,
                    name=method_name
                )
                result = list(result)
            if len(result) == 1:
                return [_convert_node(record["m"]) for record in result]
            if len(result) == 0:
                LoggerManager().logger.error(f"No nodes found for the given method name: {method_name}.")
                # print(f"No nodes found for the given method name: {method_name}.")
                return "NO MATCH FOR SOURCE METHOD"
            if len(result) > 1 and flag % 2 != 0:
                with self.driver.session() as session:
                    sub_result = session.run(
                        """
                        MATCH (m:Method)
                        WHERE m.name = $name AND m.full_qualified_name CONTAINS $full_qualified_name
                        RETURN m
                        """,
                        name=method_name, full_qualified_name=full_qualified_name
                    )
                    sub_result = list(sub_result)
                if len(sub_result) > 0:
                    sub_node_ids = [record["m"].element_id for record in sub_result]
                    if self.focal_method_id in sub_node_ids:
                        sub_node_ids.remove(self.focal_method_id)
                    with self.driver.session() as session:
                        sub_sub_result = session.run(
                            """
                            UNWIND $node_ids AS node_id
                            MATCH (base:Method), (m)
                            WHERE elementId(m) = node_id AND elementId(base) = $base_method_id
                            MATCH path = shortestPath((base)-[*..8]-(m))
                            RETURN m, length(path) AS distance
                            ORDER BY distance
                            """,
                            node_ids=sub_node_ids,
                            base_method_id=self.focal_method_id
                        )
                        sub_sub_result = list(sub_sub_result)
                    return [_convert_node(record["m"]) for record in sub_sub_result]
                if len(sub_result) == 0:
                    node_ids = [record["m"].element_id for record in result]
                    if self.focal_method_id in node_ids:
                        node_ids.remove(self.focal_method_id)
                    with self.driver.session() as session:
                        sub_result = session.run(
                            """
                            UNWIND $node_ids AS node_id
                            MATCH (base:Method), (m)
                            WHERE elementId(m) = node_id AND elementId(base) = $base_method_id
                            MATCH path = shortestPath((base)-[*..8]-(m))
                            RETURN m, length(path) AS distance
                            ORDER BY distance
                            """,
                            node_ids=node_ids,
                            base_method_id=self.focal_method_id
                        )
                        sub_result = list(sub_result)
                    return [_convert_node(record["m"]) for record in sub_result]

    def search_variable_query(self, variable_name: str, full_qualified_name: str = None) -> Union[str, List[Any]]:
        """
        查询对应的 Variable 节点。

        :param full_qualified_name:
        :param variable_name: Variable 节点的 name 属性
        :return: 1. "NO MATCH FOR SOURCE VARIABLE" : 没有找到源 Variable 节点
                2. List : 查询到的 Variable 节点列表
        """
        flag = 0
        with self.driver.session() as session:
            query = "MATCH (n:Variable)"
            # 动态添加条件
            conditions = []
            if '.' not in variable_name:
                conditions.append(f"n.name = '{variable_name}'")
            else:
                conditions.append(f"n.full_qualified_name CONTAINS '{variable_name}'")
            if full_qualified_name:
                conditions.append(f"n.full_qualified_name CONTAINS '{full_qualified_name}'")
                flag += 1
            # 将条件拼接到查询语句
            if conditions:
                query += " WHERE " + " AND ".join(conditions)
            # 返回最终查询
            query += " RETURN n"
            result = session.run(query)
            result = list(result)

        if len(result) == 0 and flag > 0:
            with self.driver.session() as session:
                result = session.run(
                    """
                    MATCH (n:Variable)
                    WHERE n.name = $name
                    RETURN n
                    """,
                    name=variable_name
                )
                result = list(result)
            if len(result) == 1:
                return [_convert_node(record["n"]) for record in result]
            if len(result) == 0:
                LoggerManager().logger.error(f"No nodes found for the given variable name: {variable_name}.")
                # print(f"No nodes found for the given variable name: {variable_name}.")
                return "NO MATCH FOR SOURCE VARIABLE"
            if len(result) > 1:
                node_ids = [record["n"].element_id for record in result]
                with self.driver.session() as session:
                    sub_result = session.run(
                        """
                        UNWIND $node_ids AS node_id
                        MATCH (base:Method), (n)
                        WHERE elementId(n) = node_id AND elementId(base) = $base_method_id
                        MATCH path = shortestPath((base)-[*..8]-(n))
                        RETURN n, length(path) AS distance
                        ORDER BY distance
                        """,
                        node_ids=node_ids,
                        base_method_id=self.focal_method_id
                    )
                    sub_result = list(sub_result)
                return [_convert_node(record["n"]) for record in sub_result]
        if len(result) == 0 and flag == 0:
            LoggerManager().logger.error(f"No nodes found for the given variable name: {variable_name}.")
            # print(f"No nodes found for the given variable name: {variable_name}.")
            return "NO MATCH FOR SOURCE VARIABLE"
        if len(result) == 1:
            return [_convert_node(record["n"]) for record in result]
        if len(result) > 1:
            node_ids = [record["n"].element_id for record in result]
            with self.driver.session() as session:
                sub_result = session.run(
                    """
                    UNWIND $node_ids AS node_id
                    MATCH (base:Method), (n)
                    WHERE elementId(n) = node_id AND elementId(base) = $base_method_id
                    MATCH path = shortestPath((base)-[*..8]-(n))
                    RETURN n, length(path) AS distance
                    ORDER BY distance
                    """,
                    node_ids=node_ids,
                    base_method_id=self.focal_method_id
                )
                sub_result = list(sub_result)
            return [_convert_node(record["n"]) for record in sub_result]

    def fuzzy_search(self, name: str) -> List[Any]:
        """
        模糊查询 Method, Variable, Clazz 节点。
        :param name:
        :return: 1. "NO MATCH FOR TARGET NODE" : 没有找到目标节点
                2. List : 查询到的节点列表
        """
        with self.driver.session() as session:
            query = "MATCH (n)"
            # 动态添加条件
            conditions = []
            if '.' not in name:
                conditions.append(f"n.name = '{name}'")
            else:
                conditions.append(f"n.full_qualified_name CONTAINS '{name}'")
            # 将条件拼接到查询语句
            if conditions:
                query += " WHERE " + " AND ".join(conditions)
            # 返回最终查询
            query += " RETURN n"
            result = session.run(query)
            result = list(result)
        if not result:
            LoggerManager().logger.error(f"No nodes found for the given name: {name}.")
            # print(f"No nodes found for the given name: {name}.")
            return "NO MATCH FOR TARGET NODE"
        return [_convert_node(record["n"]) for record in result]

    def search_method_one_hop_neighbors(self) -> Dict[str, List[Any]]:
        """
        获取 focal_method_id 节点相邻的节点，并按照关系边以及关系流向分类。
        """
        if not self.focal_method_id:
            return {}
            
        query = """
                MATCH (m:Method)
                WHERE elementId(m) = $focal_method_id

                // 第一部分: 查找直接与 Method 相连的 Clazz 节点
                OPTIONAL MATCH (m)-[:CONTAIN]-(n:Clazz)
                WITH COLLECT(DISTINCT n) AS direct_clazz_nodes, m

                // 第二部分: 查找与 Method 相连的 Clazz 节点（通过 DEPENDENCY 关系）
                OPTIONAL MATCH (m)-[:DEPENDENCY]-(c:Clazz)
                WITH direct_clazz_nodes, COLLECT(DISTINCT c) AS related_clazz_nodes, m

                // 查找 InvokeLine 节点，并获取与之相连的另一个节点
                OPTIONAL MATCH (m)-[]->(vo:Variable)
                WHERE NOT id(vo) = id(m) // 排除回到原 Method 节点的情况

                OPTIONAL MATCH (m)-[]->(mo:Method)
                WHERE NOT id(mo) = id(m) // 排除回到原 Method 节点的情况

                OPTIONAL MATCH (m)<-[]-(vi:Variable)
                WHERE NOT id(vi) = id(m) // 排除回到原 Method 节点的情况

                OPTIONAL MATCH (m)<-[]-(mi:Method)
                WHERE NOT id(mi) = id(m) // 排除回到原 Method 节点的情况

                // 聚合所有节点
                WITH direct_clazz_nodes, 
                     related_clazz_nodes,
                     COLLECT(DISTINCT vo) AS variable_nodes_outgoing,
                     COLLECT(DISTINCT vi) AS variable_nodes_incoming,
                     COLLECT(DISTINCT mo) AS method_nodes_outgoing,
                     COLLECT(DISTINCT mi) AS method_nodes_incoming

                // 返回结果
                RETURN 
                    direct_clazz_nodes AS main_clazz,
                    related_clazz_nodes AS related_clazz,
                    variable_nodes_outgoing,
                    variable_nodes_incoming,
                    method_nodes_outgoing,
                    method_nodes_incoming
                """

        parameters = {"focal_method_id": self.focal_method_id}

        try:
            with self.driver.session() as session:
                result = session.run(query, parameters)
                record = result.single()
                # 转换 Neo4j 节点为对应的类实例
                main_clazz = [_convert_node(node) for node in record["main_clazz"] or []]
                related_clazz = [_convert_node(node) for node in record["related_clazz"] or []]
                variable_nodes_outgoing = [_convert_node(node) for node in
                                           record["variable_nodes_outgoing"] or []]
                variable_nodes_incoming = [_convert_node(node) for node in
                                           record["variable_nodes_incoming"] or []]
                method_nodes_outgoing = [_convert_node(node) for node in record["method_nodes_outgoing"] or []]
                method_nodes_incoming = [_convert_node(node) for node in record["method_nodes_incoming"] or []]

                return {
                    "MainClazz": main_clazz,
                    "RelatedClazz": related_clazz,
                    "VariableOutgoing": variable_nodes_outgoing,
                    "VariableIncoming": variable_nodes_incoming,
                    "MethodOutgoing": method_nodes_outgoing,
                    "MethodIncoming": method_nodes_incoming
                }
        except Neo4jError as e:
            LoggerManager().logger.error(f"Error while querying adjacent nodes: {e}")
            LoggerManager().logger.exception("Exception occurred")
            # print(f"Error while querying adjacent nodes: {e}")
            return {}

    def search_variable_one_hop_neighbors(self, field_id) -> Dict[str, List[Any]]:
        """
        获取 focal_method_id 节点相邻的节点，进一步筛选 InvokeLine 节点及其连接的第二个节点。
        按照标签（Clazz, Variable, Method）分类返回节点列表。

        :return: 包含 Clazz, Variable, Method 三个标签的节点列表字典
        """
        query = """
                MATCH (m:Variable)
                WHERE elementId(m) = $focal_variable_id

                // 第一部分: 查找直接与 Variable 相连的 Clazz 节点
                OPTIONAL MATCH (m)-[:CONTAIN]-(n:Clazz)
                WITH COLLECT(DISTINCT n) AS direct_clazz_nodes, m

                // 第二部分: 查找与 Variable 相连的 Clazz 节点（通过 DEPENDENCY 关系）
                OPTIONAL MATCH (m)-[:DEPENDENCY]-(n:Clazz)
               WITH direct_clazz_nodes, COLLECT(DISTINCT n) AS related_clazz_nodes, m

                // 查找 InvokeLine 节点，并获取与之相连的另一个节点
                OPTIONAL MATCH (m)-[]->(vo:Variable)
                WHERE NOT id(vo) = id(m) // 排除回到原 Method 节点的情况

                OPTIONAL MATCH (m)-[]->(mo:Method)
                WHERE NOT id(mo) = id(m) // 排除回到原 Method 节点的情况

                OPTIONAL MATCH (m)<-[]-(vi:Variable)
                WHERE NOT id(vi) = id(m) // 排除回到原 Method 节点的情况

                OPTIONAL MATCH (m)<-[]-(mi:Method)
                WHERE NOT id(mi) = id(m) // 排除回到原 Method 节点的情况
                
                // 聚合所有节点
                WITH direct_clazz_nodes, 
                     related_clazz_nodes,
                     COLLECT(DISTINCT vo) AS variable_nodes_outgoing,
                     COLLECT(DISTINCT vi) AS variable_nodes_incoming,
                     COLLECT(DISTINCT mo) AS method_nodes_outgoing,
                     COLLECT(DISTINCT mi) AS method_nodes_incoming
                     
                // 最终结果: 合并 direct_clazz_nodes 和 clazz_nodes_from_invoke
                RETURN 
                    direct_clazz_nodes AS main_clazz,
                    related_clazz_nodes AS clazz_nodes,
                    variable_nodes_outgoing,
                    variable_nodes_incoming,
                    method_nodes_outgoing,
                    method_nodes_incoming
                """

        parameters = {"focal_variable_id": field_id}

        try:
            with self.driver.session() as session:
                result = session.run(query, parameters)
                record = result.single()
                # 转换 Neo4j 节点为对应的类实例
                main_clazz = [_convert_node(node) for node in record["main_clazz"] or []]
                clazz_nodes = [_convert_node(node) for node in record["clazz_nodes"] or []]
                variable_nodes_outgoing = [_convert_node(node) for node in
                                           record["variable_nodes_outgoing"] or []]
                variable_nodes_incoming = [_convert_node(node) for node in
                                           record["variable_nodes_incoming"] or []]
                method_nodes_outgoing = [_convert_node(node) for node in record["method_nodes_outgoing"] or []]
                method_nodes_incoming = [_convert_node(node) for node in record["method_nodes_incoming"] or []]

                return {
                    "MainClazz": main_clazz,
                    "Clazz": clazz_nodes,
                    "VariableOutgoing": variable_nodes_outgoing,
                    "VariableIncoming": variable_nodes_incoming,
                    "MethodOutgoing": method_nodes_outgoing,
                    "MethodIncoming": method_nodes_incoming
                }
        except Neo4jError as e:
            LoggerManager().logger.error(f"Error while querying adjacent nodes: {e}")
            LoggerManager().logger.exception("Exception occurred")
            # print(f"Error while querying adjacent nodes: {e}")
            return {"Clazz": [], "Variable": [], "Method": []}

    # def handle_1_self_graph(self, method_dict):
    #     """
    #     处理 1-self 图的节点信息，包括 Method, Variable, Clazz 节点。【目前未使用】
    #     :param method_dict:
    #     :return:
    #     """
    #     related_class = method_dict['Clazz']
    #     related_method = method_dict['Method']
    #     related_variable = method_dict['Variable']
    #     current_method = self.load_current_method()
    #     related_method.append(current_method)
    #     used_list = []
    #     main_class = None
    #     surround_class = []
    #     for clazz in related_class:
    #         content = clazz.simple_content
    #         constructor_list = self.search_constructor_in_clazz(clazz.name)
    #         for constructor in constructor_list:
    #             content = self.append_info_to_class(content, constructor.content)
    #         for variable in related_variable:
    #             if variable.class_name == clazz.name:
    #                 content = self.append_info_to_class(content, variable.content)
    #                 used_list.append(variable)
    #         for method in related_method:
    #             if method.class_name == clazz.name:
    #                 content = self.append_info_to_class(content, method.content)
    #                 used_list.append(method)
    #         if clazz.name == current_method.class_name:
    #             main_class = content
    #         else:
    #             surround_class.append(content)
    #
    #     remain_method = [self.construct_info_in_class(item.class_name, item.content) for item in related_method if
    #                      item not in used_list]
    #     remain_variable = [self.construct_info_in_class(item.class_name, item.content) for item in related_variable if
    #                        item not in used_list]
    #
    #     return main_class, surround_class, remain_method, remain_variable
    #
    # def append_info_to_class(self, class_content: str, append_info: str) -> str:
    #     """
    #     将 method_list 中的方法追加到 class_content 类的末尾
    #     :param class_content: Java 类内容字符串
    #     :param method_list: 完整方法内容的列表
    #     :return: 追加后的类内容字符串
    #     """
    #     # 定位类的结束大括号 '}'
    #     class_end_pattern = re.compile(r"\s*}\s*$")
    #     match = class_end_pattern.search(class_content)
    #
    #     if not match:
    #         raise ValueError("Invalid class content: Missing closing brace '}'.")
    #
    #     # 找到结束位置，将方法内容插入到结束大括号之前
    #     class_content = class_content[:match.start()]  # 截取掉最后的 '}'
    #     class_content += f"\n\n{append_info}"  # 追加每个方法内容
    #     class_content += "\n}"  # 加回结束大括号
    #
    #     return class_content
    #
    # def construct_info_in_class(self, class_name, info):
    #     clazz = self.search_clazz_query(class_name)
    #     clazz_signature = clazz.simple_content.split("{")[0]
    #     construct_class = clazz_signature + "{\n" + info + "\n}"
    #     return construct_class
